﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using bbv.DomainDrivenDesign.Interfaces;

namespace O1.Social.Newsgator.Repository.LinqToSql
{
    public abstract class Converter<TDataTransferObject, TEntity> : IConverter<TDataTransferObject, TEntity>
        where TDataTransferObject : class
        where TEntity : class, new()
    {
        public abstract Func<TEntity, TDataTransferObject> Convert { get; }
        public abstract Action<TDataTransferObject, TEntity> ConvertBack { get; }
        public abstract Action<TDataTransferObject, TEntity> Update { get; }
       

        protected void SetSubEntityChanges<TLinqSub, TPocoSub, TIdType>(EntitySet<TLinqSub> linq,
                                                                        IEnumerable<TPocoSub> poco,
                                                                        Expression<Func<TLinqSub, TIdType>> linqIdSelector,
                                                                        Expression<Func<TPocoSub, TIdType>> pocoIdSelector,
                                                                        TIdType pocoIdEmptyValue,
                                                                        Expression<Func<TPocoSub, bool>> newRecordSelector,
                                                                        Action<TLinqSub, TPocoSub> setSubProperties
                                                                        ) where TLinqSub : class
        {
            if (poco == null || linq == null) return;
            //Detect Creates
            var whereEmptyId = Expression.Lambda<Func<TPocoSub, bool>>(Expression.Equal(pocoIdSelector.Body,
                                                                                        Expression.Constant(pocoIdEmptyValue)),
                                                                       pocoIdSelector.Parameters).Compile();

            var whereIsNewRecord = Expression.Lambda<Func<TPocoSub, bool>>(Expression.Equal(newRecordSelector.Body,
                                                                                        Expression.Constant(true)),
                                                                       newRecordSelector.Parameters).Compile();

            var toCreate = poco.Where(whereEmptyId).Union(poco.Where(whereIsNewRecord));            

            //Detect updates
            var linqAndPocoIdEqual = Expression.Lambda<Func<TPocoSub, TLinqSub, bool>>(Expression.Equal(pocoIdSelector.Body, linqIdSelector.Body),
                                                                                       pocoIdSelector.Parameters[0], linqIdSelector.Parameters[0]).Compile();
            var toUpdate = linq.Where(l => poco.Any(p => linqAndPocoIdEqual(p, l)));

            //Construct creates
            var creates = toCreate.Select(memberType =>
                                              {
                                                  var type = Activator.CreateInstance<TLinqSub>();
                                                  setSubProperties(type, memberType);
                                                  return type;
                                              });
            //Persist updates
            toUpdate.ToList().ForEach(l =>
                                          {
                                              var map = poco.SingleOrDefault(p => linqAndPocoIdEqual(p, l));
                                              setSubProperties(l, map);
                                          });

            //Detect Deletes and remove from list
            linq.Except(toUpdate).ToList().ForEach(l => linq.Remove(l));

            //Add creates.
            linq.AddRange(creates);
        }
    }
}